"""
GT-LMS System User Manager
This module implements the user manager, using singleton pattern to manage user data
"""

from typing import Dict, List, Optional, Any
from datetime import datetime
import json
import os
from pathlib import Path
import sys

# Add project root directory to Python path
project_root = Path(__file__).parent.parent
sys.path.insert(0, str(project_root))

from models.user import User, Student, Teacher, Admin, UserRole


class UserManager:
    """User Manager - Using singleton pattern"""
    
    _instance = None
    _initialized = False
    
    def __new__(cls):
        """Singleton pattern implementation"""
        if cls._instance is None:
            cls._instance = super(UserManager, cls).__new__(cls)
        return cls._instance
    
    @classmethod
    def get_instance(cls):
        """Get singleton instance"""
        if cls._instance is None:
            cls._instance = cls()
        return cls._instance
    
    def __init__(self):
        """Initialize user manager"""
        if self._initialized:
            return
        
        self.users: Dict[str, User] = {}  # user_id -> User object
        self.email_to_user_id: Dict[str, str] = {}  # email -> user_id
        self.role_users: Dict[UserRole, List[str]] = {
            UserRole.STUDENT: [],
            UserRole.TEACHER: [],
            UserRole.ADMIN: []
        }
        # Get absolute path of project root directory
        project_root = Path(__file__).parent.parent.parent
        self.data_file = os.path.join(project_root, "gt_lms", "data", "users.json")
        self._initialized = True
        self.load_users()
    
    def create_user(self, user_type: str, user_data: Dict[str, Any]) -> Optional[User]:
        """
        Create user
        
        Parameters:
            user_type: User type ('student', 'teacher', 'admin')
            user_data: User data dictionary
            
        Returns:
            Created user object, returns None if failed
        """
        try:
            user_id = user_data.get('user_id')
            name = user_data.get('name')
            email = user_data.get('email')
            password = user_data.get('password')
            
            if not all([user_id, name, email, password]):
                print("Error: Missing required user information")
                return None
            
            # Check if user ID already exists
            if user_id in self.users:
                print(f"Error: User ID '{user_id}' already exists")
                return None
            
            # Check if email already exists
            if email in self.email_to_user_id:
                print(f"Error: Email '{email}' already in use")
                return None
            
            # Create corresponding user object based on user type
            if user_type == 'student':
                student_number = user_data.get('student_number', user_id)
                major = user_data.get('major', '')
                enrollment_year = user_data.get('enrollment_year')
                
                user = Student(
                    user_id=user_id,
                    name=name,
                    email=email,
                    password=password,
                    student_number=student_number,
                    major=major,
                    enrollment_year=enrollment_year
                )
                
            elif user_type == 'teacher':
                employee_id = user_data.get('employee_id', user_id)
                department = user_data.get('department', '')
                office_hours = user_data.get('office_hours', '')
                
                user = Teacher(
                    user_id=user_id,
                    name=name,
                    email=email,
                    password=password,
                    employee_id=employee_id,
                    department=department,
                    office_hours=office_hours
                )
                
            elif user_type == 'admin':
                admin_level = user_data.get('admin_level', 1)
                
                user = Admin(
                    user_id=user_id,
                    name=name,
                    email=email,
                    password=password,
                    admin_level=admin_level
                )
            else:
                print(f"Error: Unknown user type '{user_type}'")
                return None
            
            # Add user to manager
            self.users[user_id] = user
            self.email_to_user_id[email] = user_id
            self.role_users[user.role].append(user_id)
            
            # Save user data
            self.save_users()
            
            print(f"Successfully created user: {name} ({user_type})")
            return user
            
        except Exception as e:
            print(f"Error creating user: {e}")
            return None
    
    def get_user_by_id(self, user_id: str) -> Optional[User]:
        """
        Get user object by user ID
        
        Parameters:
            user_id: User ID
            
        Returns:
            User object, returns None if not exists
        """
        return self.users.get(user_id)
    
    def get_user(self, user_id: str) -> Optional[User]:
        """
        Get user object by user ID (alias for get_user_by_id)
        
        Parameters:
            user_id: User ID
            
        Returns:
            User object, returns None if not exists
        """
        return self.get_user_by_id(user_id)
    
    def get_user_by_email(self, email: str) -> Optional[User]:
        """
        Get user object by email
        
        Parameters:
            email: User email
            
        Returns:
            User object, returns None if not exists
        """
        user_id = self.email_to_user_id.get(email)
        if user_id:
            return self.users.get(user_id)
        return None
    
    def update_user(self, user_id: str, update_data: Dict[str, Any]) -> bool:
        """
        Update user information

        Parameters:
            user_id: User ID
            update_data: Dictionary of data to update

        Returns:
            Returns True if update successful, False if failed
        """
        user = self.get_user_by_id(user_id)
        if not user:
            print(f"Error: User '{user_id}' does not exist")
            return False
            
        try:
            # Update common fields
            allowed_fields = ['name', 'email']
            for field in allowed_fields:
                if field in update_data:
                    setattr(user, field, update_data[field])
            
            # Update role-specific fields
            if isinstance(user, Student):
                student_fields = ['student_number', 'major', 'enrollment_year']
                for field in student_fields:
                    if field in update_data:
                        setattr(user, field, update_data[field])
                        
            elif isinstance(user, Teacher):
                teacher_fields = ['employee_id', 'department', 'office_hours']
                for field in teacher_fields:
                    if field in update_data:
                        setattr(user, field, update_data[field])
                        
            elif isinstance(user, Admin):
                admin_fields = ['admin_level']
                for field in admin_fields:
                    if field in update_data:
                        setattr(user, field, update_data[field])
            
            # If email is updated, update email mapping
            if 'email' in update_data:
                # Remove old email mapping
                old_email = None
                for email, uid in self.email_to_user_id.items():
                    if uid == user_id:
                        old_email = email
                        break
                
                if old_email:
                    del self.email_to_user_id[old_email]
                
                # Add new email mapping
                self.email_to_user_id[update_data['email']] = user_id
            
            # Save user data
            self.save_users()
            
            print(f"Successfully updated user: {user.name}")
            return True
            
        except Exception as e:
            print(f"Error updating user: {e}")
            return False
    
    def delete_user(self, user_id: str) -> bool:
        """
        Delete user
        
        This method deletes the user from the system. It verifies the user exists, removes the user from all collections, and saves the updated user data.
        
        Parameters:
            user_id: User ID
            
        Returns:
            Whether the deletion was successful
        """
        user = self.get_user_by_id(user_id)
        if not user:
            print(f"Error: User '{user_id}' does not exist")
            return False
        
        try:
            # Remove from user dictionary
            del self.users[user_id]
            
            # Remove from email mapping
            email_to_delete = None
            for email, uid in self.email_to_user_id.items():
                if uid == user_id:
                    email_to_delete = email
                    break
            
            if email_to_delete:
                del self.email_to_user_id[email_to_delete]
            
            # Remove from role user list
            self.role_users[user.role].remove(user_id)
            
            # Save user data
            self.save_users()
            
            print(f"Successfully deleted user: {user_id}")
            return True
            
        except Exception as e:
            print(f"Error deleting user: {e}")
            return False
    
    def deactivate_user(self, user_id: str) -> bool:
        """
        Deactivate user account
        
        This method deactivates the user account, preventing user login while retaining their data in the system.
        
        Parameters:
            user_id: User ID
            
        Returns:
            Returns True if deactivation successful, False otherwise
        """
        user = self.get_user_by_id(user_id)
        if not user:
            print(f"Error: User '{user_id}' not found")
            return False
            
        try:
            user.is_active = False
            self.save_users()
            print(f"Successfully deactivated user: {user_id}")
            return True
        except Exception as e:
            print(f"Error deactivating user: {e}")
            return False
    
    def activate_user(self, user_id: str) -> bool:
        """
        Activate user account
        
        This method activates the user account, allowing user login.
        
        Parameters:
            user_id: User ID
            
        Returns:
            Returns True if activation successful, False otherwise
        """
        user = self.get_user_by_id(user_id)
        if not user:
            print(f"Error: User '{user_id}' not found")
            return False
            
        try:
            user.is_active = True
            self.save_users()
            print(f"Successfully activated user: {user_id}")
            return True
        except Exception as e:
            print(f"Error activating user: {e}")
            return False
    
    def authenticate_user(self, email: str, password: str) -> Optional[User]:
        """
        Authenticate user credentials
        
        This method authenticates the user by email and password. It searches for a user with the specified email,
        verifies the password, and updates the last login time if authentication is successful.
        
        Parameters:
            email: User email
            password: User password
            
        Returns:
            Returns user object if authentication successful, None if failed
        """
        user = self.get_user_by_email(email)
        if user and user.verify_password(password) and user.is_active:
            user.last_login = datetime.now()
            self.save_users()
            return user
        return None
    
    def get_users_by_role(self, role: UserRole) -> List[User]:
        """
        Get all users by role
        
        This method returns a list of users with the specified role. It filters users by role and returns the corresponding user objects.
        
        Parameters:
            role: User role
            
        Returns:
            List of users for the specified role
        """
        # Since enum objects may not be the same instance, we use value comparison to find the corresponding key
        user_ids = []
        for key, ids in self.role_users.items():
            if key.value == role.value:  # Compare enum values instead of objects
                user_ids = ids
                break
        
        user_list = []
        # Only return users that actually exist
        for user_id in user_ids:
            if user_id in self.users:
                user_list.append(self.users[user_id])
        
        return user_list
    
    def search_users(self, keyword: str) -> List[User]:
        """
        Search users by keyword (name, email, student number, employee ID)
        
        This method searches users based on keywords that can match name, email, student number (for students) or employee ID (for teachers).
        
        Parameters:
            keyword: Search keyword
            
        Returns:
            List of matching users
        """
        results = []
        keyword_lower = keyword.lower()
        
        for user in self.users.values():
            # Check name and email
            if (keyword_lower in user.name.lower() or 
                keyword_lower in user.email.lower()):
                results.append(user)
                continue
                
            # Check student number (students only)
            if isinstance(user, Student) and keyword_lower in user.student_number.lower():
                results.append(user)
                continue
                
            # Check employee ID (teachers only)
            if isinstance(user, Teacher) and keyword_lower in user.employee_id.lower():
                results.append(user)
                continue
                
        return results
    
    def get_user_count(self) -> Dict[str, int]:
        """
        Get user statistics
        
        This method returns statistics on the number of users in the system, categorized by user type.
        
        Returns:
            User count statistics by type
        """
        # Calculate the actual number of existing users
        students_count = sum(1 for user_id in self.role_users[UserRole.STUDENT] if user_id in self.users)
        teachers_count = sum(1 for user_id in self.role_users[UserRole.TEACHER] if user_id in self.users)
        admins_count = sum(1 for user_id in self.role_users[UserRole.ADMIN] if user_id in self.users)
        
        return {
            'total': len(self.users),
            'students': students_count,
            'teachers': teachers_count,
            'admins': admins_count
        }
    
    def get_all_users(self) -> List[User]:
        """
        Get all users
        
        This method returns a list of all users in the system.
        
        Returns:
            List of all users
        """
        return list(self.users.values())
    
    def save_users(self) -> bool:
        """
        Save user data to file
        
        Returns:
            Returns True if save successful, False if failed
        """
        try:
            # Ensure data directory exists
            os.makedirs(os.path.dirname(self.data_file), exist_ok=True)
            
            # Rebuild role_users dictionary to ensure no duplicates
            role_users_clean = {
                UserRole.STUDENT: [],
                UserRole.TEACHER: [],
                UserRole.ADMIN: []
            }
            
            for user_id, user in self.users.items():
                if user_id not in role_users_clean[user.role]:
                    role_users_clean[user.role].append(user_id)
            
            data = {
                'users': {},
                'email_to_user_id': self.email_to_user_id,
                'role_users': {
                    role.value: user_ids for role, user_ids in role_users_clean.items()
                },
                'last_updated': datetime.now().isoformat()
            }
            
            # Serialize user data
            for user_id, user in self.users.items():
                user_data = user.get_info()
                user_data['password'] = user._password  # Include password for verification
                
                # Add type-specific extra data
                if isinstance(user, Student):
                    user_data.update({
                        'student_number': user.student_number,
                        'major': user.major,
                        'enrollment_year': user.enrollment_year,
                        'enrolled_courses': user.enrolled_courses,
                        'gpa': user.gpa,
                        'grades': user.grades,
                        'user_type': 'student'
                    })
                elif isinstance(user, Teacher):
                    user_data.update({
                        'employee_id': user.employee_id,
                        'department': user.department,
                        'office_hours': user.office_hours,
                        'teaching_courses': user.teaching_courses,
                        'user_type': 'teacher'
                    })
                elif isinstance(user, Admin):
                    user_data.update({
                        'admin_level': user.admin_level,
                        'permissions': user.permissions,
                        'user_type': 'admin'
                    })
                
                data['users'][user_id] = user_data
            
            # Save to file
            with open(self.data_file, 'w', encoding='utf-8') as f:
                json.dump(data, f, indent=2, ensure_ascii=False)
            
            print(f"Successfully saved user data to {self.data_file}")
            return True
            
        except Exception as e:
            print(f"Error saving user data: {e}")
            return False
    
    def load_users(self) -> bool:
        """
        Load user data from file
        
        Returns:
            Returns True if load successful, False if failed
        """
        try:
            if not os.path.exists(self.data_file):
                print(f"User data file does not exist: {self.data_file}")
                return True  # File not existing is not an error
            
            with open(self.data_file, 'r', encoding='utf-8') as f:
                data = json.load(f)
            
            # Clear existing data
            self.users.clear()
            self.email_to_user_id.clear()
            # Do not clear role_users as it will be reloaded from file later
            
            # Check data format - if it's direct user data format
            if not isinstance(data, dict) or 'users' not in data:
                # Handle direct user data format
                users_data = data
                role_users_data = {}
            else:
                # Handle format with metadata
                # Restore email mapping
                self.email_to_user_id = data.get('email_to_user_id', {})
                
                # Load role_users data from file
                role_users_data = data.get('role_users', {})
                
                # Get user data
                users_data = data.get('users', {})
            
            # Clear role_users dictionary first
            self.role_users = {
                UserRole.STUDENT: [],
                UserRole.TEACHER: [],
                UserRole.ADMIN: []
            }
            
            # Rebuild user objects
            for user_id, user_data in users_data.items():
                # Get user type from role field
                role = user_data.get('role', '')
                if role == 'student':
                    user_type = 'student'
                elif role == 'teacher':
                    user_type = 'teacher'
                elif role == 'admin':
                    user_type = 'admin'
                else:
                    # If no role field, try to infer from user_id
                    if user_id.startswith('student_'):
                        user_type = 'student'
                    elif user_id.startswith('teacher_'):
                        user_type = 'teacher'
                    elif user_id.startswith('admin_'):
                        user_type = 'admin'
                    else:
                        continue
                
                if user_type == 'student':
                    user = Student(
                        user_id=user_id,
                        name=user_data['name'],
                        email=user_data['email'],
                        password=user_data.get('_password', user_data.get('password', '')),
                        student_number=user_data.get('student_number', user_id),
                        major=user_data.get('major', ''),
                        enrollment_year=user_data.get('enrollment_year', datetime.now().year)
                    )
                    user.enrolled_courses = user_data.get('enrolled_courses', [])
                    user.gpa = user_data.get('gpa', 0.0)
                    user.grades = user_data.get('grades', {})
                    
                elif user_type == 'teacher':
                    user = Teacher(
                        user_id=user_id,
                        name=user_data['name'],
                        email=user_data['email'],
                        password=user_data.get('_password', user_data.get('password', '')),
                        employee_id=user_data.get('employee_id', user_id),
                        department=user_data.get('department', ''),
                        office_hours=user_data.get('office_hours', '')
                    )
                    user.teaching_courses = user_data.get('courses_taught', [])
                    
                elif user_type == 'admin':
                    user = Admin(
                        user_id=user_id,
                        name=user_data['name'],
                        email=user_data['email'],
                        password=user_data.get('_password', user_data.get('password', '')),
                        admin_level=user_data.get('admin_level', 1)
                    )
                    user.permissions = user_data.get('permissions', [])
                else:
                    continue
                
                # Set common attributes
                user.created_at = datetime.fromisoformat(user_data.get('created_at', datetime.now().isoformat()))
                user.last_login = datetime.fromisoformat(user_data.get('last_login', datetime.now().isoformat()))
                user.is_active = user_data.get('is_active', True)
                
                # Add to manager
                self.users[user_id] = user
                self.email_to_user_id[user.email] = user_id
                
                # Add to role_users
                if user_type == 'student':
                    self.role_users[UserRole.STUDENT].append(user_id)
                elif user_type == 'teacher':
                    self.role_users[UserRole.TEACHER].append(user_id)
                elif user_type == 'admin':
                    self.role_users[UserRole.ADMIN].append(user_id)
            
            print(f"Successfully loaded {len(self.users)} users")
            return True
            
        except Exception as e:
            print(f"Error loading user data: {e}")
            return False
    
    def get_users_by_department(self, department: str) -> List[User]:
        """
        Get teacher users by department
        
        Parameters:
            department: Department name
            
        Returns:
            List of teacher users in the specified department
        """
        return [
            user for user in self.get_users_by_role(UserRole.TEACHER)
            if hasattr(user, 'department') and user.department == department
        ]
    
    def __str__(self) -> str:
        """String representation"""
        stats = self.get_user_count()
        return f"UserManager (Total users: {stats['total']}, Students: {stats['students']}, Teachers: {stats['teachers']}, Admins: {stats['admins']})"
    
    def __repr__(self) -> str:
        """Detailed representation"""
        return self.__str__()
    
    @classmethod
    def get_instance(cls):
        """Get UserManager singleton instance"""
        if cls._instance is None:
            cls._instance = cls()
        return cls._instance